package nginx

import (
	"database/sql"
	"github.com/MzoroHaHa/dev-tools/backend/app/db"
	"github.com/MzoroHaHa/dev-tools/backend/app/logger"
	"github.com/MzoroHaHa/dev-tools/backend/app/utils"
	"github.com/bytedance/sonic"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"log/slog"
	"net/http"
	"time"
)

type NgController struct {
	logger *slog.Logger
	db     *sql.DB
}

func NewNgController(group *gin.RouterGroup) *NgController {
	n := &NgController{}
	n.db = db.GetDb()
	n.logger = logger.NewLogger("ng-controller")
	group.GET("get-info", n.GetInfo)
	group.POST("saveInfo", n.saveInfo)
	group.POST("delete", n.deleteOne)
	group.GET("get-one", n.getOne)
	group.POST("save-detail", n.saveDetail)
	group.POST("test-config", n.testConfig)
	group.POST("start", n.startNginx)
	group.POST("stop", n.stopNginx)
	group.POST("reload", n.reloadNginx)
	return n
}

func (n *NgController) GetInfo(c *gin.Context) {
	n.logger.Info("get nginx info")
	rs, err := n.db.Query("select id, name, work_dir, create_date, exec_path, update_date from nginx_info t order by create_date desc")
	if err != nil {
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  err.Error(),
		})
		return
	}
	infos := make([]*BaseInfo, 0)
	for rs.Next() {
		info := BaseInfo{}
		err = rs.Scan(&info.Id, &info.Name, &info.WorkDir, &info.CreateDate, &info.ExecPath, &info.UpdateDate)
		if err == nil {
			infos = append(infos, &info)
		} else {
			n.logger.Error("error occurred when scan nginx info")
			c.JSON(200, gin.H{
				"code": 2,
				"msg":  err.Error(),
			})
			return
		}
	}
	c.JSON(http.StatusOK, gin.H{
		"code": 1,
		"data": infos,
	})
}

func (n *NgController) saveInfo(c *gin.Context) {
	n.logger.Info("save nginx info")
	b := BaseInfo{}
	e := c.ShouldBind(&b)
	if e != nil {
		n.logger.Error("error occurred when scan nginx info", "err", e)
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  e.Error(),
		})
		return
	}

	if len(b.Id) == 0 {
		// 新增
		rs, _ := n.db.Query("select id, name, work_dir, create_date, exec_path, update_date  from nginx_info t where t.work_dir = ? ", b.WorkDir)
		if rs != nil {
			defer rs.Close()
			if rs.Next() {
				c.JSON(200, gin.H{
					"code": 2,
					"msg":  "nginx workdir already exists",
				})
				return
			}

		}
		_, e = n.db.Exec("insert into nginx_info (id, name, work_dir, create_date, exec_path, update_date ) values (?,?,?,?,?,?)",
			uuid.New().String(), b.Name, b.WorkDir, time.Now(), b.ExecPath, time.Now())
	} else {
		rs, _ := n.db.Query("select id, name, work_dir, create_date, exec_path, update_date  from nginx_info t where t.work_dir = ? and t.id != ?", b.WorkDir, b.Id)
		if rs != nil {
			defer rs.Close()
			if rs.Next() {
				c.JSON(200, gin.H{
					"code": 2,
					"msg":  "nginx workdir already exists",
				})
				return
			}

		}
		_, e = n.db.Exec("update nginx_info set work_dir=? , exec_path=?, name=?, update_date=? where id=?", b.WorkDir, b.ExecPath, b.Name, time.Now(), b.Id)
	}
	if e != nil {
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  e.Error(),
		})
		return
	}
	c.JSON(200, gin.H{
		"code": 1,
	})
}

func (n *NgController) deleteOne(c *gin.Context) {
	id := c.Query("id")
	n.logger.Info("delete nginx info", "id", id)
	if len(id) == 0 {
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  "can not get id",
		})
		return
	}
	tx, e := n.db.Begin()
	if e != nil {
		handleErr(e, nil, c)
		return
	}

	e = execSqlIgnoreResult(c, tx, "delete from nginx_info where id=?", id)
	if e != nil {
		return
	}
	e = cleanOldData(c, tx, id)
	if e != nil {
		return
	}
	e = tx.Commit()
	if e != nil {
		handleErr(e, nil, c)
		return
	}
	c.JSON(200, gin.H{
		"code": 1,
	})
}

// 查询一个nginx 信息
func (n *NgController) getOne(c *gin.Context) {
	id := c.Query("id")
	n.logger.Info("get one nginx info", "id", id)
	if len(id) == 0 {
		c.JSON(200, gin.H{
			"code": 1,
			"data": nil,
		})
		return
	}
	info, err := getNginxInfo(id, n.db, n.logger)
	if err != nil {
		handleErr(err, nil, c)
		return
	}
	detail, err := getNginxConfigDetail(n.db, id)
	if err != nil {
		handleErr(err, nil, c)
		return
	}
	info.ConfigDetail = *detail
	c.JSON(200, gin.H{
		"code": 1,
		"data": info,
	})

}

func getNginxInfo(id string, db QueryDao, logger *slog.Logger) (*BaseInfo, error) {
	rs, e := db.Query("select id, name, work_dir, create_date, exec_path, update_date  from nginx_info t where t.id = ?", id)
	if e != nil {
		return nil, e
	}
	defer rs.Close()
	if rs.Next() {
		info := BaseInfo{}
		err := rs.Scan(&info.Id, &info.Name, &info.WorkDir, &info.CreateDate, &info.ExecPath, &info.UpdateDate)
		if err != nil {
			logger.Error("error occurred when scan nginx info", "error", err)

			return nil, err
		} else {
			return &info, nil
		}
	} else {
		return nil, nil
	}
}

func (n *NgController) saveDetail(c *gin.Context) {
	b := Config{}
	e := c.ShouldBind(&b)
	if e != nil {
		n.logger.Error("save nginx detail error occurred when scan nginx info", "err", e)
		handleErr(e, nil, c)
		return
	}
	n.logger.Debug("save nginx detail", "data", b)
	if len(b.Id) == 0 {
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  "can not get id",
		})
		return
	}

	tx, e := n.db.Begin()
	if e != nil {
		handleErr(e, nil, c)
		return
	}

	ngId := b.Id
	// 先清掉旧数据
	e = cleanOldData(c, tx, ngId)
	if e != nil {
		return
	}

	// 再保存新数据
	// root config
	rootConfigs := b.BasicConfigs
	if len(rootConfigs) > 0 {
		for _, rootConfig := range rootConfigs {
			e = execSqlIgnoreResult(c, tx, "INSERT INTO nginx_root_config (id, info_id, directive_code, directive_value) VALUES (?, ?, ?, ?)",
				uuid.New().String(), ngId, rootConfig.Code, rootConfig.Value)
			if e != nil {
				return
			}
		}
	}

	// http 每个nginx 配置文件只有一个 http 块
	if len(b.Http.BasicConfigs) > 0 {
		for _, config := range b.Http.BasicConfigs {
			e = execSqlIgnoreResult(c, tx, "INSERT INTO nginx_http_config (id, info_id, directive_code, directive_value) VALUES (?, ?, ?, ?)",
				uuid.New().String(), ngId, config.Code, config.Value)
			if e != nil {
				return
			}
		}
	}
	if len(b.Http.ServerConfig) > 0 {
		for _, config := range b.Http.ServerConfig {
			json, e := sonic.Marshal(config)
			if e != nil {
				handleErr(e, tx, c)
				return
			}
			e = execSqlIgnoreResult(c, tx, "INSERT INTO nginx_http_server_config (id, config_json, info_id) VALUES (?, ?, ?)",
				uuid.New().String(), json, ngId)
			if e != nil {
				return
			}
		}
	}
	if len(b.Http.UpstreamConfig) > 0 {
		for _, config := range b.Http.UpstreamConfig {
			json, e := sonic.Marshal(config)
			if e != nil {
				handleErr(e, tx, c)
				return
			}
			e = execSqlIgnoreResult(c, tx, "INSERT INTO nginx_http_upstream_config (id, config_json, info_id) VALUES (?, ?, ?)",
				uuid.New().String(), json, ngId)
			if e != nil {
				return
			}
		}
	}
	if len(b.Stream.BasicConfigs) > 0 {
		for _, config := range b.Stream.BasicConfigs {
			e = execSqlIgnoreResult(c, tx, " INSERT INTO nginx_stream_config (id, info_id, directive_code, directive_value) VALUES (?, ?, ?, ?)",
				uuid.New().String(), ngId, config.Code, config.Value)
			if e != nil {
				return
			}
		}
	}
	if len(b.Stream.ServerConfig) > 0 {
		for _, config := range b.Stream.ServerConfig {
			json, e := sonic.Marshal(config)
			if e != nil {
				handleErr(e, tx, c)
				return
			}

			e = execSqlIgnoreResult(c, tx, "INSERT INTO nginx_stream_server_config (id, config_json, info_id) VALUES (?, ?, ?)",
				uuid.New().String(), json, ngId)
			if e != nil {
				return
			}
		}
	}
	if len(b.Stream.UpstreamConfig) > 0 {
		for _, config := range b.Stream.UpstreamConfig {
			json, e := sonic.Marshal(config)
			if e != nil {
				handleErr(e, tx, c)
				return
			}
			e = execSqlIgnoreResult(c, tx, "INSERT INTO nginx_stream_upstream_config (id, config_json, info_id) VALUES (?, ?, ?)",
				uuid.New().String(), json, ngId)
		}

	}

	e = testConfig(ngId, tx, n.logger)
	if e != nil {
		handleErr(e, tx, c)
		return
	}

	e = writeConfig(ngId, n.db, n.logger)
	if e != nil {
		c.JSON(200, gin.H{
			"code": 2,
			"msg":  "Saving nginx success, but can't write config files.",
		})
		tx.Rollback()
		return
	}
	e = tx.Commit()

	if e != nil {
		handleErr(e, nil, c)
		return
	}
	c.JSON(200, gin.H{
		"code": 1,
	})
}

func getNginxConfigDetail(_db QueryDao, ngId string) (*Config, error) {
	detailConfig := Config{}
	rootConfigRs, e := _db.Query("select directive_code, directive_value  from nginx_root_config where info_id=?", ngId)
	if e != nil {
		return nil, e
	}
	defer rootConfigRs.Close()
	rootConfigs := make([]Directive, 0)
	var rc Directive
	for rootConfigRs.Next() {
		rc = Directive{}
		e = rootConfigRs.Scan(&rc.Code, &rc.Value)
		if e != nil {
			return nil, e
		}
		rootConfigs = append(rootConfigs, rc)
	}
	detailConfig.BasicConfigs = rootConfigs

	// http
	httpRootRs, e := _db.Query("select directive_code, directive_value  from nginx_http_config where info_id=?", ngId)
	if e != nil {
		return nil, e
	}
	defer httpRootRs.Close()
	httpRoot := make([]Directive, 0)
	for httpRootRs.Next() {
		hrc := Directive{}
		e = httpRootRs.Scan(&hrc.Code, &hrc.Value)
		if e != nil {
			return nil, e
		}
		httpRoot = append(httpRoot, hrc)
	}

	// http servers
	httpServerRs, e := _db.Query("select config_json from nginx_http_server_config where info_id=?", ngId)
	if e != nil {
		return nil, e
	}
	defer httpServerRs.Close()
	httpServers := make([]HttpServerConfig, 0)
	var js string
	for httpServerRs.Next() {
		e := httpServerRs.Scan(&js)
		if e != nil {
			return nil, e
		}
		server := HttpServerConfig{}
		e = sonic.Unmarshal([]byte(js), &server)
		if e != nil {
			return nil, e
		}
		httpServers = append(httpServers, server)
	}

	// http upstream
	httpUpstreamRs, e := _db.Query("select config_json from nginx_http_upstream_config where info_id=?", ngId)
	if e != nil {
		return nil, e
	}
	defer httpUpstreamRs.Close()
	httpUpstreams := make([]UpstreamConfig, 0)
	var s string
	for httpUpstreamRs.Next() {
		e := httpUpstreamRs.Scan(&s)
		if e != nil {
			return nil, e
		}
		httpUpstream := UpstreamConfig{}
		e = sonic.Unmarshal([]byte(s), &httpUpstream)
		if e != nil {
			return nil, e
		}
		httpUpstreams = append(httpUpstreams, httpUpstream)
	}
	detailConfig.Http = HttpConfig{
		BasicConfigs:   httpRoot,
		ServerConfig:   httpServers,
		UpstreamConfig: httpUpstreams,
	}
	// stream
	streamRootRs, e := _db.Query("select directive_code, directive_value  from nginx_stream_config where info_id=?", ngId)
	if e != nil {
		return nil, e
	}
	defer streamRootRs.Close()
	streamRoot := make([]Directive, 0)
	for streamRootRs.Next() {
		hrc := Directive{}
		e = streamRootRs.Scan(&hrc.Code, &hrc.Value)
		if e != nil {
			return nil, e
		}
		streamRoot = append(streamRoot, hrc)
	}
	// stream server
	streamServerRs, e := _db.Query("select config_json from nginx_stream_server_config where info_id=?", ngId)
	if e != nil {
		return nil, e
	}
	defer streamServerRs.Close()
	streamServers := make([]StreamServerConfig, 0)
	var sjs string
	for streamServerRs.Next() {
		e := streamServerRs.Scan(&sjs)
		if e != nil {
			return nil, e
		}
		server := StreamServerConfig{}
		e = sonic.Unmarshal([]byte(sjs), &server)
		if e != nil {
			return nil, e
		}
		streamServers = append(streamServers, server)
	}

	streamUpstreamRs, e := _db.Query("select config_json from nginx_stream_upstream_config where info_id=?", ngId)
	if e != nil {
		return nil, e
	}
	defer streamUpstreamRs.Close()
	streamUpstreams := make([]UpstreamConfig, 0)
	var sss string
	for streamUpstreamRs.Next() {
		e := streamUpstreamRs.Scan(&sss)
		if e != nil {
			return nil, e
		}
		streamUpstream := UpstreamConfig{}
		e = sonic.Unmarshal([]byte(sss), &streamUpstream)
		if e != nil {
			return nil, e
		}
		streamUpstreams = append(streamUpstreams, streamUpstream)
	}
	detailConfig.Stream = StreamConfig{
		BasicConfigs:   streamRoot,
		ServerConfig:   streamServers,
		UpstreamConfig: streamUpstreams,
	}
	return &detailConfig, nil
}
func cleanOldData(c *gin.Context, tx *sql.Tx, ngId string) error {
	e := execSqlIgnoreResult(c, tx, "delete from nginx_root_config where info_id=?", ngId)
	if e != nil {
		return e
	}
	e = execSqlIgnoreResult(c, tx, "delete from nginx_http_config where info_id=?", ngId)
	if e != nil {
		return e
	}
	e = execSqlIgnoreResult(c, tx, "delete from nginx_http_server_config where info_id=?", ngId)
	if e != nil {
		return e
	}
	e = execSqlIgnoreResult(c, tx, "delete from nginx_http_upstream_config where info_id=?", ngId)
	if e != nil {
		return e
	}
	e = execSqlIgnoreResult(c, tx, "delete from nginx_stream_config where info_id=?", ngId)
	if e != nil {
		return e
	}
	e = execSqlIgnoreResult(c, tx, "delete from nginx_stream_server_config where info_id=?", ngId)
	if e != nil {
		return e
	}
	e = execSqlIgnoreResult(c, tx, "delete from nginx_stream_upstream_config where info_id=?", ngId)
	if e != nil {
		return e
	}
	return nil
}

func execSqlIgnoreResult(c *gin.Context, tx *sql.Tx, sql string, args ...interface{}) error {
	_, e := tx.Exec(sql, args...)
	if e != nil {
		handleErr(e, tx, c)
	}
	return e
}

func handleErr(e error, tx *sql.Tx, c *gin.Context) {
	handleErrWithMsg(e, tx, c, "")
}

func handleErrWithMsg(e error, tx *sql.Tx, c *gin.Context, msg string) {
	if e != nil {
		if tx != nil {
			tx.Rollback()
		}
		if c != nil {
			_msg := utils.FirstNotEmpty(msg, e.Error())
			c.JSON(200, gin.H{
				"code": 2,
				"msg":  _msg,
			})
		}
	}
}
